package com.gframework.mybatis.dao.mybatis.plugins;

import java.lang.reflect.Method;
import java.util.Properties;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.lang.Nullable;

/**
 * 通用删除拦截器
 * 
 * @since 1.0.0
 * @author Ghwolf
 * @see DeleteHandle
 * @see DeleteInfo
 */
@Intercepts(@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }))
public class DeleteHelper implements Interceptor {
	private static final Logger logger = LoggerFactory.getLogger(DeleteHelper.class);

	/**
	 * 本类对象
	 */
	private static DeleteHelper instance ;
	
	/**
	 * 删除操作默认拦截对象.<br>
	 * 如果没有做其他特殊配置，那么默认使用此对象作为删除的拦截操作对象
	 */
	@Nullable
	private DeleteHandle defaultDeleteHandle;
	/**
	 * 全局拦截策略，如果没有指定，则会走默认策略：<br>
	 * true表示默认进行拦截处理(这将意味着所有删除逻辑都会)，false表示不进行拦截处理（默认）。
	 */
	private final boolean doInterceptor;
	
	/**
	 * @param doInterceptor 全局拦截策略，如果没有指定，则会走默认策略：<br>
	 * true表示默认进行拦截处理(这将意味着所有删除逻辑都会)，false表示不进行拦截处理（默认）。
	 */
	public DeleteHelper(boolean doInterceptor,@Nullable DeleteHandle defaultDeleteHandle){
		this.doInterceptor = doInterceptor;
		this.defaultDeleteHandle = defaultDeleteHandle;
		if (instance != null) {
			throw new BeanInitializationException("DeleteHelper应当是单例的，但是却被创建了两次");
		} else {
			logger.info("====> 已启用 DeleteHelper 逻辑删除mybatis插件，使用本类可以实现任意删除操作转逻辑删除！");
		}
		instance = this;
	}

	/**
	 * 操作处理的本地线程变量
	 */
	private ThreadLocal<ThreadHandleInfo> threadHandleInfo = new ThreadLocal<>();

	/**
	 * 当前线程的自定义变量：doInterceptor和deleteHandle封装类
	 * 
	 * @author Ghwolf
	 */
	private static class ThreadHandleInfo {

		// 仅作封装，没有其他作用，所以并没有实现太多方法。
		Boolean doInterceptor;
		DeleteHandle deleteHandle;
		// 如果为true，则表示这是一个长期生效的拦截配置
		boolean always = false ;
	}

	/**
	 * 取得当前线程内的ThreadHandleInfo类对象，如果没有则会新实例化一个。
	 * 
	 * @return 返回当前线程的ThreadHandleInfo类对象
	 */
	private ThreadHandleInfo getLocalThreadHandleInfo() {
		ThreadHandleInfo info = this.threadHandleInfo.get();
		if (info == null) {
			info = new ThreadHandleInfo();
			this.threadHandleInfo.set(info);
		}
		return info;
	}

	/**
	 * 删除当前线程的ThreadHandleInfo类对象.
	 */
	private void removeLocalThreadHandleInfo() {
		ThreadHandleInfo t = this.threadHandleInfo.get();
		if (t != null && !t.always) {
			this.threadHandleInfo.remove();
		}
	}

	/**
	 * 调用此方法来长期开启删除拦截操作，直到调用{@link #stopInterceptor()}方法为止，当前线程期间所有的删除操作均会被拦截处理.
	 * <p>通常本方法应用在aop上，通过try...finally来进行统一的逻辑删除处理，而后在finally中释放。
	 * @param deleteHandle 指定一个拦截删除对象
	 */
	public static void startInterceptor(DeleteHandle deleteHandle) {
		ThreadHandleInfo info = DeleteHelper.instance.getLocalThreadHandleInfo();
		info.doInterceptor = Boolean.TRUE;
		info.deleteHandle = deleteHandle;
		info.always = true ;
	}
	/**
	 * 调用此方法来长期开启删除拦截操作，直到调用{@link #stopInterceptor()}方法为止，当前线程期间所有的删除操作均会被拦截处理.
	 * <p>通常本方法应用在aop上，通过try...finally来进行统一的逻辑删除处理，而后在finally中释放。
	 */
	public static void startInterceptor() {
		ThreadHandleInfo info = DeleteHelper.instance.getLocalThreadHandleInfo();
		info.doInterceptor = Boolean.TRUE;
		info.always = true ;
	}
	/**
	 * 调用此方法来停止删除拦截操作，既停止{@link #startInterceptor()}、{@link #startInterceptor(DeleteHandle)}设置的长期拦截设置.
	 * <p>通常本方法应用在aop的finally中用于释放配置
	 */
	public static void stopInterceptor() {
		DeleteHelper.instance.threadHandleInfo.remove();
	}
	
	/**
	 * 执行一个操作，此操作过程中的删除均会进行拦截处理
	 * @param consumer 消费型函数式接口
	 * @param t 参数，可以为null
	 * @throws NullPointerException 如果consumer为null
	 */
	public static <T> void doDelete(Consumer<T> consumer,T t) {
		try {
			startInterceptor();
			consumer.accept(t);
		} finally {
			stopInterceptor();
		}
	}
	/**
	 * 执行一个操作，此操作过程中的删除均会进行拦截处理
	 * @param function 函数式接口
	 * @param t 参数，可以为null
	 * @throws NullPointerException 如果function为null
	 */
	public static <T,R> R doDelete(Function<T,R> function,T t) {
		try {
			startInterceptor();
			return function.apply(t);
		} finally {
			stopInterceptor();
		}
	}
	/**
	 * 执行一个操作，此操作过程中的删除均会进行拦截处理
	 * @param function 函数式接口
	 * @param t 参数，可以为null
	 * @throws NullPointerException 如果function为null
	 */
	public static <T,U,R> R doDelete(BiFunction<T,U,R> function,T t,U u) {
		try {
			startInterceptor();
			return function.apply(t, u);
		} finally {
			stopInterceptor();
		}
	}
	
	/**
	 * 执行一个操作，此操作过程中的删除均会进行拦截处理
	 * @param deleteHandle 指定一个拦截删除对象
	 * @param consumer 消费型函数式接口
	 * @param t 参数，可以为null
	 * @throws NullPointerException 如果consumer为null
	 */
	public static <T> void doDelete(DeleteHandle deleteHandle,Consumer<T> consumer,T t) {
		try {
			startInterceptor(deleteHandle);
			consumer.accept(t);
		} finally {
			stopInterceptor();
		}
	}
	/**
	 * 执行一个操作，此操作过程中的删除均会进行拦截处理
	 * @param deleteHandle 指定一个拦截删除对象
	 * @param function 函数式接口
	 * @param t 参数，可以为null
	 * @throws NullPointerException 如果function为null
	 */
	public static <T,R> R doDelete(DeleteHandle deleteHandle,Function<T,R> function,T t) {
		try {
			startInterceptor(deleteHandle);
			return function.apply(t);
		} finally {
			stopInterceptor();
		}
	}
	/**
	 * 执行一个操作，此操作过程中的删除均会进行拦截处理
	 * @param deleteHandle 指定一个拦截删除对象
	 * @param function 函数式接口
	 * @param t 参数，可以为null
	 * @throws NullPointerException 如果function为null
	 */
	public static <T,U,R> R doDelete(DeleteHandle deleteHandle,BiFunction<T,U,R> function,T t,U u) {
		try {
			startInterceptor(deleteHandle);
			return function.apply(t, u);
		} finally {
			stopInterceptor();
		}
	}
	/**
	 * 执行一个操作，此操作过程中的删除均会进行拦截处理
	 * @param deleteHandle 指定一个拦截删除对象
	 * @param supplier 函数式接口
	 * @throws NullPointerException 如果supplier为null
	 */
	public static <R> R doDelete(DeleteHandle deleteHandle,Supplier<R> supplier) {
		try {
			startInterceptor(deleteHandle);
			return supplier.get();
		} finally {
			stopInterceptor();
		}
	}
	
	/**
	 * 设置本此操作是否拦截以及拦截处理对象.<br>
	 * 本方法是线程安全的，当前的设置只会在本线程内有效。并且持续一次效果后重置。
	 * 但是请注意，你需要在设置完之后立刻执行删除操作，才不会出现问题，如果你设置了但是没有执行删除操作，一旦此线程被在此使用的时候，设置效果并不会消失。
	 * 
	 * @param isInterceptor true拦截，false不拦截
	 * @param deleteHandle 拦截删除对象
	 */
	public static void setInterceptor(boolean isInterceptor, DeleteHandle deleteHandle) {
		ThreadHandleInfo info = DeleteHelper.instance.getLocalThreadHandleInfo();
		info.doInterceptor = Boolean.valueOf(isInterceptor);
		info.deleteHandle = deleteHandle;
	}

	/**
	 * 设置本此是否执行拦截操作.<br>
	 * 本方法是线程安全的，当前的设置只会在本线程内有效。并且持续一次效果后重置。
	 * 但是请注意，你需要在设置完之后立刻执行删除操作，才不会出现问题，如果你设置了但是没有执行删除操作，一旦此线程被在此使用的时候，设置效果并不会消失。
	 * 
	 * @param isInterceptor true拦截，false不拦截
	 */
	public static void setInterceptor(boolean isInterceptor) {
		ThreadHandleInfo info = DeleteHelper.instance.getLocalThreadHandleInfo();
		info.doInterceptor = Boolean.valueOf(isInterceptor);
	}

	/**
	 * 设置本次拦截操作的处理对象.<br>
	 * 本方法是线程安全的，当前的设置只会在本线程内有效。并且持续一次效果后重置。
	 * 但是请注意，你需要在设置完之后立刻执行删除操作，才不会出现问题，如果你设置了但是没有执行删除操作，一旦此线程被在此使用的时候，设置效果并不会消失。
	 * 
	 * @param deleteHandle 拦截删除对象
	 */
	public static void setDeleteHandle(DeleteHandle deleteHandle) {
		ThreadHandleInfo info = DeleteHelper.instance.getLocalThreadHandleInfo();
		info.deleteHandle = deleteHandle;
	}

	/**
	 * 判断是否执行拦截操作.<br>
	 * 
	 * @return true表示拦截，false表示不拦截
	 */
	private boolean isInterceptor() {
		ThreadHandleInfo info = this.getLocalThreadHandleInfo();
		return info.doInterceptor == null ? this.doInterceptor : info.doInterceptor;
	}

	/**
	 * 取得删除拦截处理处理接口对象
	 * 
	 * @return DeleteHandle接口对象，可能为null，为null应当不做拦截处理
	 */
	@Nullable
	private DeleteHandle getDeleteHandle() {
		ThreadHandleInfo info = this.getLocalThreadHandleInfo();
		return info.deleteHandle == null ? this.defaultDeleteHandle : info.deleteHandle;
	}

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		try {
			MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
			String type = ms.getSqlCommandType().name();
			if ("DELETE".equals(type)) {
				// 如果是delete操作
				DeleteHandle deleteHandle = this.getDeleteHandle();
				if (deleteHandle == null || !this.isInterceptor()) {
					// deleteHandle为null
					if (logger.isWarnEnabled()) {
						logger.warn("缺少DeleteHandle，无法执行逻辑删除拦截！");
					}
					return invocation.proceed();
				}

				DeleteInfo deleteInfo = new LazyLoadingDeleteInfo(invocation);
				if (logger.isInfoEnabled()) {
					Method m = invocation.getMethod();
					String name = m.getDeclaringClass().getName() + "#" + m.getName(); 
					logger.info("本次删除操作被拦截修改为逻辑删除！删除表：{},执行方法：{}，删除拦截器：{}",deleteInfo.getTableName(),name,deleteInfo.getClass().getName());
				}
				// 执行拦截操作
				return deleteHandle.interceptor(deleteInfo);
			} else {
				// 不是delete操作
				return invocation.proceed();
			}
		} finally {
			// 删除本地变量
			this.removeLocalThreadHandleInfo();
		}
	}

	/**
	 * 只拦截Executor
	 */
	@Override
	public Object plugin(Object target) {
		if (target instanceof Executor) {
			return Plugin.wrap(target, this);
		} else {
			return target;
		}
	}

	/**
	 * @deprecated
	 * 请结合springboot方式配置
	 */
	@Override
	@Deprecated
	public void setProperties(Properties properties) {
	}

}
